// decoder.cpp
// Copyright (C) 2008 Sam Bateman
// samuel.bateman@gmail.com
//
// This file is part of DVD On Fire.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

//! @file decoder.cpp
//! @author Sam Bateman
//! @brief Source file for the Decoder class.

#include <cstdio>
#include <cstring>
#include <string>
#include <sys/stat.h>
#include <tivodecode/tivodecoder.h>
#include <libdvdonfire/config.h>
#include <libdvdonfire/log.h>
#include <libdvdonfire/util.h>
#include "decoder.h"
using namespace dvdonfire::lib;
using namespace std;

namespace dvdonfire
{
    namespace daemon
    {
        // myfread(mem, size, fh) {{{
        int myfread(void *mem, int size, void *fh)
        {
            return fread(mem, 1, size, (FILE *)fh);
        }
        // }}}
        // myfwrite(mem, size, fh) {{{
        int myfwrite(void *mem, int size, void *fh)
        {
            return fwrite(mem, 1, size, (FILE *)fh);
        }
        // }}}

        // struct Decoder::Impl {{{
        struct Decoder::Impl
        {
            FILE *fp_in;
            FILE *fp_out;
        };
        // }}}
        // Decoder::Decoder() {{{
        //! @brief Constructor.
        Decoder::Decoder()
        : Step("Decoder")
        {
            impl = new Impl;
            impl->fp_in = NULL;
            impl->fp_out = NULL;
        }
        // }}}
        // Decoder::~Decoder() {{{
        //! @brief Destructor.
        Decoder::~Decoder()
        {
            if (impl->fp_in) fclose(impl->fp_in);
            if (impl->fp_out) fclose(impl->fp_out);
            delete impl;
        }
        // }}}
        // Decoder::cleanup() {{{
        //! @brief Removes the .TiVo file.
        void Decoder::cleanup()
        {
        }
        // }}}
        // Decoder::doStep() {{{
        //! @brief Converts the .TiVo file to .mpg.
        //!
        //! Uses libtivodecode and some fancy stuff to decode the TiVo video
        //! file and output an mpeg file.  This function runs in a separate
        //! thread.
        void Decoder::doStep()
        {
            // get the files and mak info
            Config *config = getData()->getConfig();
            string temp_dir = config->get("confdir") + "tmp/";
            string input_file = temp_dir + "dtin.TiVo";
            string output_file = temp_dir + "dtout.mpg";
            char *mak = (char *)config->get("mak").c_str();

            // get the file size
            logInfo("Running stat() on " + input_file);
            struct stat s;
            int r = stat(input_file.c_str(), &s);
            if (r != 0) throw StepError("stat() returned " + numToStr(r));
            off_t file_size = s.st_size;
            logInfo("File size is " + numToStr(file_size));

            // open the files
            logInfo("Opening for reading " + input_file);
            impl->fp_in = fopen(input_file.c_str(), "rb");
            if (!impl->fp_in){
                throw StepError("Couldn't open file " + input_file);
            }
            logInfo("Opening for writing " + output_file);
            impl->fp_out = fopen(output_file.c_str(), "wb");
            if (!impl->fp_out){
                throw StepError("Couldn't open file " + output_file);
            }

            // create turing info
            logInfo("Running init_turing_from_file()");
            turing_state turing;
            memset(&turing, 0, sizeof(turing_state));
            r = init_turing_from_file(&turing, impl->fp_in, myfread, mak);
            if (r == -1) throw StepError("init_turing_from_file() failed");
            fseek(impl->fp_in, r, SEEK_SET);
            logInfo("Seeking to " + numToStr(r));

            // read and write mpeg frames
            unsigned int marker = 0xffffffff;
            unsigned char byte;
            bool first = true;
            bool done = false;
            while (!done){
                if ((marker & 0xffffff00) == 0x100){
                    off_t pos = ftello(impl->fp_in);
                    r = process_frame(byte, &turing, pos,
                                      impl->fp_in, myfread,
                                      impl->fp_out, myfwrite);
                    if (r == 1) marker = 0xffffffff;
                    else if (r == 0) fwrite(&byte, 1, 1, impl->fp_out);
                    else if (r < 0) throw StepError("process_frame() returned"
                                                    + numToStr(r));
                    setProgress((float)pos / file_size);
                }
                else if (!first){
                    fwrite(&byte, 1, 1, impl->fp_out);
                }
                marker <<= 8;
                if (fread(&byte, 1, 1, impl->fp_in) != 0){
                    marker |= byte;
                }
                else {
                    done = true;
                }
                first = false;
                if (yield()) break;
            }
            destruct_turing(&turing);
        }
        // }}}
    }
}

// libtivodecode needs these
int o_verbose = 0;
int o_no_verify = 0;
